#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string>


#include "visualisator.h"
//#include "Vector/vector.03.h"
//#include "Color/color.01.h"

//#include "Timer/timer.01.h"
//#include "FileRAWReader/frr.01.h"
#include <vector> 
#include <deque> 

using namespace std;



class MyVisualizator: public Visualizator {
	public:
		
		float *color;
		float *pos;
		int n;
		bool type1F;
		bool type2F;
		bool bufferDataF;
		bool tempF;
		
		MyVisualizator():pos(0),color(0), n(0) {
	
			perspectiveF = false;
			scale = -2;
			pointSize = 1;
			grid_color = Color(0.7,0.7,0.7,0.7);
			grid_size = 100;
			grid_step = 3;
			gridF = false;

			type1F = true;
			type2F = true;
			
			bufferDataF = false;

		};
		
		virtual void printInfo(){
			
			//Visualizator::printInfo();
			
			char str[512];
			FileNameDecomposer fnd;
			fnd.decompose(sslist[ssid]);
			sprintf(str, "%s\n", fnd.name.c_str());
			glColor3f (.4,.4,.4);
			glPrint(10,20, str, GLUT_BITMAP_HELVETICA_12);
			
		}
		
		
		void paintGL() {
			
			int n_max = 250000;
			//return;
			Visualizator::paintGL();
			
			if (!sslist.empty()) {
				
				glColor3fv(color);
				
				glEnable(GL_POINT_SPRITE_ARB);
				glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
				glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_NV);
				glDepthMask(GL_TRUE);
				glEnable(GL_DEPTH_TEST);
				
				glUseProgram(program);

				glUniform1i(glGetUniformLocation(program, "perspectiveF"), perspectiveF);
				glUniform1f(glGetUniformLocation(program, "pointRadius"), pointSize*pow(10, scale)*height());
				glUniform1f(glGetUniformLocation(program, "pointScale"), 1.0/(2*tan(fov*0.5f*(float)M_PI/180.0f)));

				int bi = 0; 
				int ni = n; 
				float *posi = pos;
				float *colori = color;

				do {
					
					if (!bufferDataF) {
						
						// copy data to GPU
						glBindBuffer(GL_ARRAY_BUFFER_ARB, bi+1);
						glBufferData(GL_ARRAY_BUFFER_ARB, (n_max < ni ? n_max : ni)*3*sizeof(float), posi, GL_STATIC_DRAW_ARB);

						glBindBuffer(GL_ARRAY_BUFFER_ARB, bi+2);
						glBufferData(GL_ARRAY_BUFFER_ARB, (n_max < ni ? n_max : ni)*3*sizeof(float), colori, GL_STATIC_DRAW_ARB);
						
						if (ni < n_max) bufferDataF = true;
					}
					
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, bi+1);
					glVertexPointer(3, GL_FLOAT, 0, 0);
					glEnableClientState(GL_VERTEX_ARRAY);		
					
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, bi+2);
					glColorPointer(3, GL_FLOAT, 0, 0);
					glEnableClientState(GL_COLOR_ARRAY);	
					
					glDrawArrays(GL_POINTS, 0, n_max < ni ? n_max : ni);
					
					glDisableClientState(GL_COLOR_ARRAY);
					glDisableClientState(GL_VERTEX_ARRAY); 
					
					posi+= 3*n_max;
					colori+= 3*n_max;
					ni -= n_max;
					bi+=2;
					
				} while (0 < ni);

				glUseProgram(0);
				glDisable(GL_POINT_SPRITE_ARB);
				
			}
			
		}
		
		
		void keyPressEvent(QKeyEvent *event){
			Visualizator::keyPressEvent(event);
			
			if (event->key() == Qt::Key_PageUp)	scale+= -1.0/scale_steps;
			if (event->key() == Qt::Key_PageDown) scale+= +1.0/scale_steps;
			if (event->key() == Qt::Key_Home) if (pointSize<500) pointSize*=1.0625;
			if (event->key() == Qt::Key_End) if (0.01<pointSize) pointSize/=1.0625;
			
			if (event->key() == Qt::Key_Left) if (0<ssid) read_ss(--ssid);
			if (event->key() == Qt::Key_Right) if (ssid<sslist.size()-1) read_ss(++ssid);
			
			if (event->key() == Qt::Key_1)  { type1F=!type1F; read_ss(ssid); } 
			if (event->key() == Qt::Key_2)  { type2F=!type2F; read_ss(ssid); } 
			
			
			if (event->key() == Qt::Key_R) resize(1920, 1200);
			
			
			if (event->key() == Qt::Key_I && QApplication::keyboardModifiers() == 0) {
				QImage image = grabFrameBuffer();
				image.save((sslist[ssid]+".png").c_str(), "PNG"); // writes image into ba in PNG format
			}
			
			if (event->key() == Qt::Key_I && QApplication::keyboardModifiers() == Qt::ControlModifier) {
				
				int ssid_tmp = ssid;
				
				for (ssid=0; ssid<sslist.size(); ssid++) {
					
					read_ss(ssid);
					updateGL();
					QImage image = grabFrameBuffer();
					image.save((sslist[ssid]+".png").c_str(), "PNG"); // writes image into ba in PNG format
					printf("File %s is saved.\n", (sslist[ssid]+".png").c_str());
				}
				
				ssid = ssid_tmp;
				read_ss(ssid);
			}
			
			if (event->key() == Qt::Key_R) resize(1920, 1080);
			
			
			if (event->key() == Qt::Key_U)  {
				read_ss(ssid);
				updateGL();
			}
			
		}
		class FileReader {
			public:
			
			FileReader(): mem(0){	}
				
			int getFileSize(const char* filename){
				struct stat results;
				stat(filename, &results);
				return results.st_size;
			}

			char *mem;
			int filesize;
		
			void read(string filename) {
		
				filesize = getFileSize(filename.c_str());

				if (mem) delete [] mem;
				mem = new char[filesize];
		
				FILE *fs = fopen(filename.c_str(), "r");
				if (fs==NULL) {
					printf("\033[22;31merror: can't read the file\033[22;35m '%s'\033[22;30m\n", filename.c_str()); 
					return;
				}
			
				fread(mem, 1, filesize, fs); 
			
				fclose(fs);
				
			}
			
		} fileReader;
		
		
		class FileListFinder {
			public:
				vector < string > list;
				
				int find(string dirname) {
					
					DIR *dir;
					dirent *ent; // entry
					
					if ((dir=opendir(dirname.c_str())) == NULL) { perror("Error: can't open directory"); return -1; }
			
					list.erase(list.begin(), list.begin()+list.size());
			
					while ((ent=readdir(dir)) != NULL)
					list.push_back(string(ent->d_name));		
		
					closedir(dir);
					
					return 0;
			
				}
		
				void print() {
					printf("\n");
					for (uint i = 0; i<list.size();i++)
					cout << list[i] << endl;
				}
				
		} filelistfinder;
		
		vector<string> sslist;
		
		class FileNameDecomposer {
			public:
				string filename;
				string path;
				string name;
				string basename;
				string extname;
				
				void decompose(string filename){
					
					this->filename = filename;
					
					path = filename.find_last_of("/")!=-1 ? filename.substr(0, filename.find_last_of("/")+1) : "./";
					name = filename.find_last_of("/")!=-1 ? filename.substr(filename.find_last_of("/")+1, filename.length()) : filename;
					basename = name.find_first_of("0123456789")!=-1 ? name.substr(0, name.find_first_of("0123456789")) : name;
					extname = name.find_last_of("0123456789")!=-1 ? name.substr(name.find_last_of("0123456789")+1, name.length()) : "";
					
				}
				
				void print(){
					
					printf("filename = %s\n", filename.c_str());
					printf("path = %s\n", path.c_str());
					printf("name = %s\n", name.c_str());
					printf("basename = %s\n", basename.c_str());
					printf("extname = %s\n", extname.c_str());		
					
				}
		}; 
		
		FileNameDecomposer fnd;
		int ssid;
		void readfile(string filename) {
			
			fnd.decompose(filename);
			
			filelistfinder.find(fnd.path);
			
			// select
			for (int i=0; i<filelistfinder.list.size(); i++)  {
				
				FileNameDecomposer fndi;
				fndi.decompose(filelistfinder.list[i]);
				
				if (fnd.basename==fndi.basename && fnd.extname==fndi.extname)
					sslist.push_back(filelistfinder.list[i]);	
				
			}
		
			// sort
			for (uint i=0; i<sslist.size()-1; i++) {	
						
				uint jmin(0);
				string strmin = sslist[i];
				for (uint j=i+1; j<sslist.size(); j++)
					if (sslist[j] < strmin) { 
						strmin=sslist[j]; 
						jmin=j;
					}
						
				if (jmin) {
					string tmp = sslist[jmin];
					sslist[jmin] = sslist[i];
					sslist[i] = tmp;
				}
					
			}
			
			// get curr file id
			for (uint i=0; i<sslist.size(); i++)
				if (fnd.name==sslist[i]) ssid=i;
			
			// add path back
			for (uint i=0; i<sslist.size(); i++)
				sslist[i] = fnd.path + sslist[i];

			read_ss(ssid);
		}
		
	private:
		
		void read_ss(int ssid) {
			
			//printf("list[%i]=%s\n", ssid, sslist[ssid].c_str());
			
			fileReader.read(sslist[ssid]);
			
			int n = fileReader.filesize/(4*4);
			//printf("n=%i\n",n);
			
			float *pos = new float[3*n];
			float *color = new float[3*n];
			
			for (int i=0; i<n; i++) {
				
				pos[3*i+0] = ((float*)fileReader.mem)[4*i+0];
				pos[3*i+1] = ((float*)fileReader.mem)[4*i+1];
				pos[3*i+2] = ((float*)fileReader.mem)[4*i+2];
				
				if (((float*)fileReader.mem)[4*i+3] == 0) {
					
					color[3*i+0] = 0.25;
					color[3*i+1] = 0.25;
					color[3*i+2] = 0.25;
					if (!type1F) pos[3*i+0]= pos[3*i+1] = pos[3*i+2] = 1000;
					
				}
				
				if (((float*)fileReader.mem)[4*i+3] == 1) {
					
					color[3*i+0] = 1;
					color[3*i+1] = 0;
					color[3*i+2] = 0;
					
					if (!type2F) pos[3*i+0]= pos[3*i+1] = pos[3*i+2] = 1000;
				}
				
			}
			
			float *pos_old = this->pos;
			float *color_old = this->color;
			
			this->n = n;
			this->pos = pos;
			this->color = color;
			
			if (pos_old) delete [] pos_old;
			if (color_old) delete [] color_old;
		
			bufferDataF = false;
			
		}
		
};



int main(int argc, char *argv[]) {
	
	glutInit(&argc, argv); //initialize glut
	QApplication app(argc, argv);
	
	MyVisualizator visualizator;

	
	if (argc>1) { 
		visualizator.readfile(argv[1]); 
	
		visualizator.resize(640, 480);
		visualizator.setWindowTitle( QString("MD Visualizator") );
		visualizator.show();
	
		return app.exec();
	
	} else fprintf(stderr, "\033[22;31mError: no file specified!\033[22;30m\n");
	
	return 0;
}
